#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Author: leuelcey (wyd)
# E-mail: leuelcey@163.com
# Creation date: 2019.8
# Modified date: 2021.3

import shutil
from pickle import load as PKLoad, dump as PKDump, HIGHEST_PROTOCOL

class Config(object):
    """
        按传统配置文件的操作习惯处理二进制数据文件，包含二进制配置文件的建立、修改、读取，
    以及文件内容的概览、存储格式及数据结构定义的显示，等等功能。
        传统配置文件(.ini)的格式：
            [段名1]
            变量1=<值1>
            变量2=<值2>
            ...
            [段名2]
            变量1=<值1>
            变量2=<值2>
            ...
    """
    __slots__ = ('Hprotocol')

    def __init__(self):
        super(Config, self).__init__()
        self.Hprotocol = HIGHEST_PROTOCOL

    def scheme(self):
        """
        显示定义的存储格式及数据结构。
        用法：scheme()
        返回：无。
        """
        dataformat = (
            "\n"
            "   显示定义的存储格式及数据结构：\n"
            "  #=================================================================\n"
            "  {\n"
            "      '段1': {\n"
            "          '变量1': (值1, 值2...),                  # tuple 型\n"
            "          '变量2': [值1, 值2...],                  # list 型\n"
            "          '变量3': 10.222,                         # float 型\n"
            "          '变量4': 91,                             # int 型\n"
            "          '变量5': 'abc123',                       # string 型\n"
            "          '变量6': False,                          # boolean 型\n"
            "          '变量7': {                               # dict 型\n"
            "                  '子变量1': 值1,\n"
            "                  '子变量2': 值2,\n"
            "                  ...\n"
            "          },\n"
            "          ...\n"
            "      },\n"
            "      '段2': {\n"
            "          ...\n"
            "      },\n"
            "      '段3': {\n"
            "          ...\n"
            "      },\n"
            "      ...\n"
            "  }\n"
            "  #=================================================================\n"
        )
        print(dataformat)

    def getConfig(self, *, filename, sec_name, var_name='', sub_name='', sub_part=-1):
        """
        取配置文件相应的数据内容。
        用法：p, err = getConfig(filename, sec_name[, var_name[, sub_name | sub_part]])
        参数：
            - filename：配置文件名称，必需。
            - sec_name：段名，必须。若为"ALL"，则取全部配置内容。
            - var_name：变量名，可选。若提供，则只返回该名称的配置。
            - sub_name：子变量名，可选。若提供，则名称为 var_name 的变量必须为 dict 类型。
            - sub_part：索引，可选。若提供，则名称为 var_name 的变量必须为 list 或 tuple 类型。
        返回：段内容及结果信息。
        """
        if not filename:
            return None, "The configuration file is not given!"
        if not sec_name:
            return None, "No section name given!"

        try:
            with open(filename, "rb") as fxload:
                header = PKLoad(fxload, encoding="utf-8")

                if not header["section"] or len(header["section"]) < 1:
                    return None, "The list of section names is empty!"
                if sec_name.upper() == "ALL":
                    configs = {}
                    for k in header["section"]:
                        configs[k] = PKLoad(fxload, encoding="utf-8")
                    return configs, ""
                elif sec_name not in header["section"]:
                    return None, "No content for the given section!"
                else:
                    configs = None
                    for k in header["section"]:
                        if k == sec_name:
                            configs = PKLoad(fxload, encoding="utf-8")
                            if var_name != '' and var_name in configs:
                                if sub_name != '' or sub_part >= 0:
                                    if isinstance(configs[var_name], dict) and sub_name in configs[var_name]:
                                        return configs[var_name][sub_name], ''
                                    if isinstance(configs[var_name], (tuple, list)) and sub_part < len(configs[var_name]):
                                        return configs[var_name][sub_part], ''
                                return configs[var_name], ''
                            return configs, ''
                        else:
                            PKLoad(fxload, encoding="utf-8")
                    return configs, ''
        except Exception as err:
            return None, err

    def newConfig(self, *, filename, configs):
        """
        新建配置文件。
        用法：success, message = newConfig(filename=配置文件, configs=内容)
        参数：
            - filename：配置文件名称，必需。
            - configs：配置内容，必需。
        返回：执行结果及其信息。
        """
        if not filename:
            return None, "The configuration file is not given!"
        if not configs or len(configs) < 1:
            return None, "The configuration content is not given!"
        if len(configs.keys()) < 1:
            return None, "The list of section names is empty!"

        header = {"section": list(configs.keys())}
        try:
            with open(filename, "wb") as fxdump:
                PKDump(header, fxdump, protocol=self.Hprotocol)
                for k in header["section"]:
                    config = configs[k]
                    PKDump(config, fxdump, protocol=self.Hprotocol)
            return 1, ""
        except Exception as err:
            return None, err

    def content(self, *, filename):
        """
        显示配置文件内容。
        用法：content(filename=配置文件)
        参数：
            - filename：配置文件，必需。
        返回：无。
        """
        if not filename:
            print("  The configuration file is not given!")
            return

        def getShow(cfgvalue):
            if cfgvalue is None:
                return ""

            svalue = str(cfgvalue)
            showex = ""
            if len(svalue) > 50 and isinstance(cfgvalue, (dict, list, tuple)):
                endchr = "}" if svalue[0] == "{" else ("]" if svalue[0] == "[" else (")" if svalue[0] == "(" else ""))
                showex = svalue[:51] + " ..." + endchr
            elif len(svalue) > 50:
                showex = svalue[:51] + " ..."
            else:
                showex = svalue
            return showex

        try:
            with open(filename, "rb") as fxshow:
                header = PKLoad(fxshow, encoding="utf-8")
                if not header or ("section" not in header) or (len(header["section"]) < 1):
                    print("  The list of section names is empty!")
                    return

                print("\n  Showing the content of the configuration file:")
                print("    " + filename)
                print("  #=============================================================\n")
                for k in header["section"]:
                    print("  [" + k + "]")
                    config = PKLoad(fxshow, encoding="utf-8")
                    cs = ""
                    if isinstance(config, dict):
                        for ks, vs in config.items():
                            cs = getShow(vs)
                            print("  " + ks + " = " + cs)
                    else:
                        cs = getShow(config)
                        print("  " + cs)
                    print("")
                print("  #=============================================================\n")
        except Exception as err:
            print(err)
            return

    def setConfig(self, *, filename, sec_name, configs, var_name=''):
        """
        修改配置文件内容。
        用法：success, message = setConfig(filename, sec_name, configs, var_name)
        参数：
            - filename：配置文件，必需。
            - sec_name：段名，必需。
            - configs：欲修改或添加的内容，必需。
            - var_name：变量名，可选。提供时 p[sec_name][var_name]=configs，不提供时 p[sec_name]=configs。
        返回：
        """
        if not filename:
            return None, 'The configuration file is not given!'
        if not sec_name:
            return None, 'No section name given!'
        if not configs:
            return None, 'The configuration content is not given!'

        configd, err = self.getConfig(filename=filename, sec_name='ALL')
        if not configd:
            configd = {}
        if sec_name not in configd:
            if var_name != '':
                configd[sec_name] = {}
                configd[sec_name][var_name] = configs
            else:
                configd[sec_name] = configs
        else:
            if var_name != '':
                configd[sec_name][var_name] = configs
            else:
                configd[sec_name] = configs

        if shutil.os.path.exists(filename):
            shutil.move(filename, filename+'~')

        success, message = self.newConfig(filename=filename, configs=configd)
        return success, message

